package cn.learn.dubbo3.enhance.processor;

import cn.learn.dubbo3.enhance.annotation.ServiceRefrence;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.dubbo.config.spring.Constants;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.config.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor;
import org.apache.dubbo.config.spring.reference.ReferenceAttributes;
import org.apache.dubbo.config.spring.reference.ReferenceBeanManager;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static com.alibaba.spring.util.AnnotationUtils.getAnnotationAttributes;
import static org.apache.dubbo.config.spring.util.SpringCompatUtils.getPropertyValue;

/**
 * @Desc
 * @Author XieWei
 * @Date 2022/1/26 15:45
 */
public class DubboServicePorxyBeanRegisterPostProcessor implements ApplicationContextAware, InstantiationAwareBeanPostProcessor {
    protected final Log logger = LogFactory.getLog(getClass());

    private final Map<String, ServiceRefrenceInjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);
    private ApplicationContext applicationContext;
    private BeanDefinitionRegistry beanDefinitionRegistry;
    private final Class<? extends Annotation>[] annotationTypes = new Class[]{ServiceRefrence.class};

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
        ServiceRefrenceInjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
        try {
            prepareInjection(metadata);
            metadata.inject(bean, beanName, pvs);
        }
        catch (BeanCreationException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
        }

        //以fieldName作为beanName
        String referenceBeanName = "";
        //注解上的属性
        Map<String, Object> attributes = new HashMap<>();
        //要注入的接口class
        Class<?> interfaceClass = null;//field.getType();
        String interfaceName = interfaceClass.getName();


        // Register the reference bean definition to the beanFactory
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName(ReferenceBean.class.getName());
        beanDefinition.getPropertyValues().add(ReferenceAttributes.ID, referenceBeanName);

        // set attribute instead of property values
        beanDefinition.setAttribute(Constants.REFERENCE_PROPS, attributes);
        beanDefinition.setAttribute(ReferenceAttributes.INTERFACE_CLASS, interfaceClass);
        beanDefinition.setAttribute(ReferenceAttributes.INTERFACE_NAME, interfaceName);

        // create decorated definition for reference bean, Avoid being instantiated when getting the beanType of ReferenceBean
        // see org.springframework.beans.factory.support.AbstractBeanFactory#getTypeForFactoryBean()
        GenericBeanDefinition targetDefinition = new GenericBeanDefinition();
        targetDefinition.setBeanClass(interfaceClass);
        String id = getPropertyValue(beanDefinition.getPropertyValues(), ReferenceAttributes.ID);

        beanDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, id+"_decorated"));

        // signal object type since Spring 5.2
        beanDefinition.setAttribute(Constants.OBJECT_TYPE_ATTRIBUTE, interfaceClass);

        beanDefinitionRegistry.registerBeanDefinition(referenceBeanName, beanDefinition);

        return pvs;
    }

    private ServiceRefrenceInjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
        // Fall back to class name as cache key, for backwards compatibility with custom callers.
        String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
        // Quick check on the concurrent map first, with minimal locking.
        ServiceRefrenceInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            synchronized (this.injectionMetadataCache) {
                metadata = this.injectionMetadataCache.get(cacheKey);
                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                    if (metadata != null) {
                        metadata.clear(pvs);
                    }
                    metadata = buildAutowiringMetadata(clazz);
                    this.injectionMetadataCache.put(cacheKey, metadata);
                }
            }
        }
        return metadata;
    }

    private ServiceRefrenceInjectionMetadata buildAutowiringMetadata(final Class<?> beanClazz) {
        final List<ServiceRefrenceFieldElement> elements = new LinkedList<ServiceRefrenceFieldElement>();

        ReflectionUtils.doWithFields(beanClazz, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                for (Class<? extends Annotation> annotationType : getAnnotationTypes()) {
                    //获取字段上面的@ServiceRefrence注解的属性
                    AnnotationAttributes attributes = getAnnotationAttributes(field, annotationType);
                    if (attributes != null) {
                        if (Modifier.isStatic(field.getModifiers())) {
                            if (logger.isWarnEnabled()) {
                                logger.warn("@" + annotationType.getName() + " is not supported on static fields: " + field);
                            }
                            return;
                        }

                        elements.add(new ServiceRefrenceFieldElement(field, null,attributes));
                    }
                }
            }
        });
        return new ServiceRefrenceInjectionMetadata(beanClazz,elements);
    }

    private AnnotationAttributes getAnnotationAttributes(Field field, Class<? extends Annotation> annotationType) {
        Annotation annotation = AnnotationUtils.getAnnotation(field,annotationType);
        return AnnotationUtils.getAnnotationAttributes(field,annotation);
    }

    private Class<? extends Annotation>[] getAnnotationTypes() {
        return annotationTypes;
    }


    protected void prepareInjection(ServiceRefrenceInjectionMetadata metadata) throws BeansException {
//        try {
//            //find and register bean definition for @DubboReference/@Reference
//            for (ServiceRefrenceFieldElement fieldElement : metadata.getFieldElements()) {
//                if (fieldElement.injectedObject != null) {
//                    continue;
//                }
//                Class<?> injectedType = fieldElement.field.getType();
//                AnnotationAttributes attributes = fieldElement.attributes;
//                String referenceBeanName = registerReferenceBean(fieldElement.getPropertyName(), injectedType, attributes, fieldElement.field);
//
//                //associate fieldElement and reference bean
//                fieldElement.injectedObject = referenceBeanName;
//                injectedFieldReferenceBeanCache.put(fieldElement, referenceBeanName);
//            }
//
//        } catch (ClassNotFoundException e) {
//            throw new BeanCreationException("prepare reference annotation failed", e);
//        }
    }

}
